/* Declarations for high-level IO functions.

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#ifndef __KP_IO__
#define __KP_IO__   1

#include "interp.hpp"
#include "stream.hpp"
#include "initop.hpp"
#include "str.hpp"
#include "function.hpp"
#include "symbol.hpp"
#include "array.hpp"
#include "utils/lazy.hpp"

KP_DECLS_BEGIN

struct reader
{
  interpreter *interp;
  valref pairs_valref;
  local_varobj<array> pairs;
  object stpairs[16];
  uint32_t pair_cnt;
  char stbuf[256];
  char *bufp;
  int bufcnt;
  int bufmax;
  int toktype;
  stream *src;
  uint32_t lineno = 0;
  int bq_level = 0;
  bool unquoted = false;
  package *ipkg;

  reader (interpreter *interp, object input, package *ipkg = nullptr);

  bool readable_p () const
    {
      return (!this->src->eos_p () && !this->src->err_p ());
    }

  void take ();
  result<void> handle_sym (object pkg, object name);
  result<bool> read_token (schar& ch, int digs);

  void expand ();

  object getlbl (object label) const;
  object* putlbl (object label);

  result<bool> nextc (schar& ch);
  void push_ch (const schar& ch);
  result<uint32_t> peek ();

  result<object> read_sexpr ();
  result<object> read_sexpr (object label);
  result<object> read_list (object label);
  result<object> read_bq (object label);
  result<object> read_comma (object label);
  result<object> read_array (object label);
  result<object> read_table (object label);
  result<object> read_tuple (object label);
  result<object> read_bvector ();
  result<object> read_str ();
  result<object> read_char ();

  ~reader ();
};

// Expand a string (With possible interpolation).
KP_EXPORT result<object> expand_str (interpreter *interp, object str);

// Expand a backquote expression.
KP_EXPORT result<object> expand_bq (interpreter *interp, object form);

// Write an object to a stream, using the parameters in INFO.
KP_EXPORT result<int64_t> xwrite (interpreter *interp, stream *strm,
                                  object obj, io_info& info);

inline result<int64_t>
xwrite (interpreter *interp, stream *strm, object obj)
{
  io_info info;
  return (xwrite (interp, strm, obj, info));
}

template <typename ...Args>
result<object> sprintf_helper (interpreter *interp, const char *fmt,
                               Args... args)
{
  local_varobj<string> sf;
  uint32_t sp = interp->stklen ();
  sf.local_init (fmt);

  KP_VTRY (interp->growstk (2 + sizeof... (args)),
           intern (interp, "%fmt-str", as_package (root_package)));

  *interp->stkend++ = symval (interp->retval);
  *interp->stkend++ = sf.as_obj ();
  KP_VTRY (push_all_helper (interp, args...));
  return (call_n (interp, interp->stklen () - sp - 1));
}

#define KP_SPRINTF(Interp, Fmt, ...)   \
  KP_CALL_DISP (KP_CALL, (Interp), khipu::sprintf_helper, (Fmt), ##__VA_ARGS__)

// Object (de)serialization.

enum
{
  PACK_NIL           = 0x74,
  PACK_EMPTY_ARRAY   = 0x75,
  PACK_EMPTY_BVECTOR = 0x76,
  PACK_EMPTY_STR     = 0x77,
  PACK_INT8          = 0x78,
  PACK_FIXINT        = 0x79,
  PACK_CHAR8         = 0x7a,
  PACK_REF_INT32     = 0x7b,
  PACK_REF_OBJ       = 0x7c,
  PACK_TYPESPEC      = 0x7d,
  PACK_END           = 0x7e,
};

struct pack_cache
{
  local_varobj<array> l_obj;
  object st_tab[256];
  valref ref;
  uint32_t n_elem;
  uint32_t n_old;
  bool evict;

  uint32_t size () const;
  object* data ();
  pack_cache (interpreter *interp);
  object get (interpreter *interp, object key);
  result<void> put (interpreter *interp, object key, object val);
};

struct pack_info
{
  valref map;
  valref offset;
  lazy<pack_cache> pcache;
  pack_cache *cache;
  stream *bstream;
  const char *errmsg;

  pack_info (interpreter *interp);
  result<void> init (interpreter *interp, bool cache);

  result<void> add_mapping (interpreter *interp, object key, object val);
  object get (interpreter *interp, object obj);
  void touch (interpreter *interp, int offset);

  exception error (const char *msg)
    {
      this->errmsg = msg;
      return (exception ());
    }

  ~pack_info ()
    {
      if (this->cache)
        destroy (this->cache);
    }

  struct eviction_guard
    {
      bool *oldp;

      eviction_guard (pack_info& info, bool use)
        {
          if (use && info.cache->evict)
            *(this->oldp = &info.cache->evict) = false;
          else
            this->oldp = nullptr;
        }

      ~eviction_guard ()
        {
          if (this->oldp)
            *this->oldp = true;
        }
    };
};

KP_EXPORT result<int64_t> xpack (interpreter *interp, stream *strm,
                                 object obj, pack_info& info);

KP_EXPORT result<int64_t> xpack (interpreter *interp, stream *strm,
                                 object obj, object *map, size_t nmap);

template <typename ...Args>
inline result<int64_t> xpack_with (interpreter *interp, stream *strm,
                                   object obj, Args... args)
{
  object tmp[] = { args..., 0 };
  return (xpack (interp, strm, obj, tmp, KP_NELEM (tmp) - 1));
}

inline result<int64_t> xpack (interpreter *interp, stream *strm, object obj)
{
  return (xpack (interp, strm, obj, nullptr, 0));
}

KP_EXPORT result<object> xunpack (interpreter *interp,
                                  stream *strm, pack_info& info);

inline result<object> xunpack (interpreter *interp, stream *strm)
{
  pack_info info { interp };
  KP_VTRY (info.init (interp, false));
  return (xunpack (interp, strm, info));
}

// Print a full backtrace starting from FRAME to stream STRMP.
KP_EXPORT result<void> print_backtrace (interpreter *interp, uint32_t frame,
                                        stream *strmp, io_info& info);

// Safely write a raised object to a stream.
KP_EXPORT void write_exc (interpreter *interp, stream *strm,
                          object exc, io_info& info);

KP_EXPORT init_op init_io;

KP_DECLS_END

#endif
